/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the  "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/*
 * $Id$
 */
package wx.xml.xalan.xpath;

import javax.xml.transform.TransformerException;

import wx.xml.xalan.xalan.res.XSLMessages;
import wx.xml.xalan.xpath.objects.XObject;
import wx.xml.xalan.xpath.res.XPATHErrorResources;

/**
 * Defines a class to keep track of a stack for
 * template arguments and variables.
 *
 * <p>This has been changed from the previous incarnations of this
 * class to be fairly low level.</p>
 *
 * @xsl.usage internal
 */
public class VariableStack implements Cloneable {
    /**
     * limitation for 1K
     */
    public static final int CLEARLIMITATION = 1024;
    /**
     * NEEDSDOC Field m_nulls
     */
    private static XObject[] m_nulls = new XObject[CLEARLIMITATION];
    /**
     * The stack frame where all variables and params will be kept.
     *
     * @serial
     */
    XObject[] _stackFrames;
    /**
     * The top of the stack frame (<code>_stackFrames</code>).
     *
     * @serial
     */
    int _frameTop;
    /**
     * The stack of frame positions.  I call 'em links because of distant
     * <a href="http://math.millikin.edu/mprogers/Courses/currentCourses/CS481-ComputerArchitecture/cs481.Motorola68000.html">
     * Motorola 68000 assembler</a> memories.  :-)
     *
     * @serial
     */
    int[] _links;
    /**
     * The top of the links stack.
     */
    int _linksTop;
    /**
     * The bottom index of the current frame (relative to <code>_stackFrames</code>).
     *
     * @serial
     */
    private int _currentFrameBottom;

    /**
     * Constructor for a variable stack.
     */
    public VariableStack() {
        reset();
    }

    /**
     * Constructor for a variable stack.
     *
     * @param initStackSize The initial stack size.  Must be at least one.  The
     *                      stack can grow if needed.
     */
    public VariableStack(int initStackSize) {
        // Allow for twice as many variables as stack link entries
        reset(initStackSize, initStackSize * 2);
    }

    /**
     * Returns a clone of this variable stack.
     *
     * @return a clone of this variable stack.
     * @throws CloneNotSupportedException
     */
    public synchronized Object clone() throws CloneNotSupportedException {

        VariableStack vs = (VariableStack) super.clone();

        // I *think* I can get away with a shallow clone here?
        vs._stackFrames = (XObject[]) _stackFrames.clone();
        vs._links = (int[]) _links.clone();

        return vs;
    }

    /**
     * Get the element at the given index, regardless of stackframe.
     *
     * @param i index from zero.
     * @return The item at the given index.
     */
    public XObject elementAt(final int i) {
        return _stackFrames[i];
    }

    /**
     * Get size of the stack.
     *
     * @return the total size of the execution stack.
     */
    public int size() {
        return _frameTop;
    }

    /**
     * Reset the stack to a start position.
     */
    public void reset() {
        // If the stack was previously allocated, assume that about the same
        // amount of stack space will be needed again; otherwise, use a very
        // large stack size.
        int linksSize = (_links == null) ? XPathContext.RECURSIONLIMIT
                                         : _links.length;
        int varArraySize = (_stackFrames == null) ? XPathContext.RECURSIONLIMIT * 2
                                                  : _stackFrames.length;
        reset(linksSize, varArraySize);
    }

    /**
     * Reset the stack to a start position.
     *
     * @param linksSize    Initial stack size to use
     * @param varArraySize Initial variable array size to use
     */
    protected void reset(int linksSize, int varArraySize) {
        _frameTop = 0;
        _linksTop = 0;

        // Don't bother reallocating _links array if it exists already
        if (_links == null) {
            _links = new int[linksSize];
        }

        // Adding one here to the stack of frame positions will allow us always
        // to look one under without having to check if we're at zero.
        // (As long as the caller doesn't screw up link/unlink.)
        _links[_linksTop++] = 0;

        // Get a clean _stackFrames array and discard the old one.
        _stackFrames = new XObject[varArraySize];
    }

    /**
     * Get the position from where the search should start,
     * which is either the searchStart property, or the top
     * of the stack if that value is -1.
     *
     * @return The current stack frame position.
     */
    public int getStackFrame() {
        return _currentFrameBottom;
    }

    /**
     * Set the current stack frame.
     *
     * @param sf The new stack frame position.
     */
    public void setStackFrame(int sf) {
        _currentFrameBottom = sf;
    }

    /**
     * Allocates memory (called a stackframe) on the stack; used to store
     * local variables and parameter arguments.
     *
     * <p>I use the link/unlink concept because of distant
     * <a href="http://math.millikin.edu/mprogers/Courses/currentCourses/CS481-ComputerArchitecture/cs481.Motorola68000.html">
     * Motorola 68000 assembler</a> memories.</p>
     *
     * @param size The size of the stack frame allocation.  This ammount should
     *             normally be the maximum number of variables that you can have allocated
     *             at one time in the new stack frame.
     * @return The bottom of the stack frame, from where local variable addressing
     * should start from.
     */
    public int link(final int size) {

        _currentFrameBottom = _frameTop;
        _frameTop += size;

        if (_frameTop >= _stackFrames.length) {
            XObject newsf[] = new XObject[_stackFrames.length + XPathContext.RECURSIONLIMIT + size];

            System.arraycopy(_stackFrames, 0, newsf, 0, _stackFrames.length);

            _stackFrames = newsf;
        }

        if (_linksTop + 1 >= _links.length) {
            int newlinks[] = new int[_links.length + (CLEARLIMITATION * 2)];

            System.arraycopy(_links, 0, newlinks, 0, _links.length);

            _links = newlinks;
        }

        _links[_linksTop++] = _currentFrameBottom;

        return _currentFrameBottom;
    }

    /**
     * Free up the stack frame that was last allocated with
     * {@link #link(int size)}.
     */
    public void unlink() {
        _frameTop = _links[--_linksTop];
        _currentFrameBottom = _links[_linksTop - 1];
    }

    /**
     * Free up the stack frame that was last allocated with
     * {@link #link(int size)}.
     *
     * @param currentFrame The current frame to set to
     *                     after the unlink.
     */
    public void unlink(int currentFrame) {
        _frameTop = _links[--_linksTop];
        _currentFrameBottom = currentFrame;
    }

    /**
     * Set a local variable or parameter in the current stack frame.
     *
     * @param index Local variable index relative to the current stack
     *              frame bottom.
     * @param val   The value of the variable that is being set.
     */
    public void setLocalVariable(int index, XObject val) {
        _stackFrames[index + _currentFrameBottom] = val;
    }

    /**
     * Set a local variable or parameter in the specified stack frame.
     *
     * @param index Local variable index relative to the current stack
     *              frame bottom.
     *              NEEDSDOC @param stackFrame
     * @param val   The value of the variable that is being set.
     */
    public void setLocalVariable(int index, XObject val, int stackFrame) {
        _stackFrames[index + stackFrame] = val;
    }

    /**
     * Get a local variable or parameter in the current stack frame.
     *
     * @param xctxt The XPath context, which must be passed in order to
     *              lazy evaluate variables.
     * @param index Local variable index relative to the current stack
     *              frame bottom.
     * @return The value of the variable.
     * @throws TransformerException
     */
    public XObject getLocalVariable(XPathContext xctxt, int index)
        throws TransformerException {

        index += _currentFrameBottom;

        XObject val = _stackFrames[index];

        if (null == val)
            throw new TransformerException(XSLMessages.createXPATHMessage(XPATHErrorResources.ER_VARIABLE_ACCESSED_BEFORE_BIND, null),
                xctxt.getSAXLocator());
        // "Variable accessed before it is bound!", xctxt.getSAXLocator());

        // Lazy execution of variables.
        if (val.getType() == XObject.CLASS_UNRESOLVEDVARIABLE)
            return (_stackFrames[index] = val.execute(xctxt));

        return val;
    }

    /**
     * Get a local variable or parameter in the current stack frame.
     *
     * @param index Local variable index relative to the given
     *              frame bottom.
     *              NEEDSDOC @param frame
     * @return The value of the variable.
     * @throws TransformerException
     */
    public XObject getLocalVariable(int index, int frame)
        throws TransformerException {

        index += frame;

        XObject val = _stackFrames[index];

        return val;
    }

    /**
     * Get a local variable or parameter in the current stack frame.
     *
     * @param xctxt The XPath context, which must be passed in order to
     *              lazy evaluate variables.
     * @param index Local variable index relative to the current stack
     *              frame bottom.
     * @return The value of the variable.
     * @throws TransformerException
     */
    public XObject getLocalVariable(XPathContext xctxt, int index, boolean destructiveOK)
        throws TransformerException {

        index += _currentFrameBottom;

        XObject val = _stackFrames[index];

        if (null == val)
            throw new TransformerException(XSLMessages.createXPATHMessage(XPATHErrorResources.ER_VARIABLE_ACCESSED_BEFORE_BIND, null),
                xctxt.getSAXLocator());
        // "Variable accessed before it is bound!", xctxt.getSAXLocator());

        // Lazy execution of variables.
        if (val.getType() == XObject.CLASS_UNRESOLVEDVARIABLE)
            return (_stackFrames[index] = val.execute(xctxt));

        return destructiveOK ? val : val.getFresh();
    }

    /**
     * Tell if a local variable has been set or not.
     *
     * @param index Local variable index relative to the current stack
     *              frame bottom.
     * @return true if the value at the index is not null.
     * @throws TransformerException
     */
    public boolean isLocalSet(int index) throws TransformerException {
        return (_stackFrames[index + _currentFrameBottom] != null);
    }

    /**
     * Use this to clear the variables in a section of the stack.  This is
     * used to clear the parameter section of the stack, so that default param
     * values can tell if they've already been set.  It is important to note that
     * this function has a 1K limitation.
     *
     * @param start The start position, relative to the current local stack frame.
     * @param len   The number of slots to be cleared.
     */
    public void clearLocalSlots(int start, int len) {

        start += _currentFrameBottom;

        System.arraycopy(m_nulls, 0, _stackFrames, start, len);
    }

    /**
     * Set a global variable or parameter in the global stack frame.
     *
     * @param index Local variable index relative to the global stack frame
     *              bottom.
     * @param val   The value of the variable that is being set.
     */
    public void setGlobalVariable(final int index, final XObject val) {
        _stackFrames[index] = val;
    }

    /**
     * Get a global variable or parameter from the global stack frame.
     *
     * @param xctxt The XPath context, which must be passed in order to
     *              lazy evaluate variables.
     * @param index Global variable index relative to the global stack
     *              frame bottom.
     * @return The value of the variable.
     * @throws TransformerException
     */
    public XObject getGlobalVariable(XPathContext xctxt, final int index)
        throws TransformerException {

        XObject val = _stackFrames[index];

        // Lazy execution of variables.
        if (val.getType() == XObject.CLASS_UNRESOLVEDVARIABLE)
            return (_stackFrames[index] = val.execute(xctxt));

        return val;
    }

    /**
     * Get a global variable or parameter from the global stack frame.
     *
     * @param xctxt The XPath context, which must be passed in order to
     *              lazy evaluate variables.
     * @param index Global variable index relative to the global stack
     *              frame bottom.
     * @return The value of the variable.
     * @throws TransformerException
     */
    public XObject getGlobalVariable(XPathContext xctxt, final int index, boolean destructiveOK)
        throws TransformerException {

        XObject val = _stackFrames[index];

        // Lazy execution of variables.
        if (val.getType() == XObject.CLASS_UNRESOLVEDVARIABLE)
            return (_stackFrames[index] = val.execute(xctxt));

        return destructiveOK ? val : val.getFresh();
    }

    /**
     * Get a variable based on it's qualified name.
     * This is for external use only.
     *
     * @param xctxt The XPath context, which must be passed in order to
     *              lazy evaluate variables.
     * @param qname The qualified name of the variable.
     * @return The evaluated value of the variable.
     * @throws javax.xml.transform.TransformerException
     */
    public XObject getVariableOrParam(
        XPathContext xctxt, wx.xml.xalan.xml.utils.QName qname)
        throws javax.xml.transform.TransformerException {

        wx.xml.xalan.xml.utils.PrefixResolver prefixResolver =
            xctxt.getNamespaceContext();

        // Get the current ElemTemplateElement, which must be pushed in as the
        // prefix resolver, and then walk backwards in document order, searching
        // for an xsl:param element or xsl:variable element that matches our
        // qname.  If we reach the top level, use the StylesheetRoot's composed
        // list of top level variables and parameters.

        if (prefixResolver instanceof wx.xml.xalan.xalan.templates.ElemTemplateElement) {

            wx.xml.xalan.xalan.templates.ElemVariable vvar;

            wx.xml.xalan.xalan.templates.ElemTemplateElement prev =
                (wx.xml.xalan.xalan.templates.ElemTemplateElement) prefixResolver;

            if (!(prev instanceof wx.xml.xalan.xalan.templates.Stylesheet)) {
                while (!(prev.getParentNode() instanceof wx.xml.xalan.xalan.templates.Stylesheet)) {
                    wx.xml.xalan.xalan.templates.ElemTemplateElement savedprev = prev;

                    while (null != (prev = prev.getPreviousSiblingElem())) {
                        if (prev instanceof wx.xml.xalan.xalan.templates.ElemVariable) {
                            vvar = (wx.xml.xalan.xalan.templates.ElemVariable) prev;

                            if (vvar.getName().equals(qname))
                                return getLocalVariable(xctxt, vvar.getIndex());
                        }
                    }
                    prev = savedprev.getParentElem();
                }
            }

            vvar = prev.getStylesheetRoot().getVariableOrParamComposed(qname);
            if (null != vvar)
                return getGlobalVariable(xctxt, vvar.getIndex());
        }

        throw new javax.xml.transform.TransformerException(XSLMessages.createXPATHMessage(XPATHErrorResources.ER_VAR_NOT_RESOLVABLE, new Object[]{qname.toString()})); //"Variable not resolvable: " + qname);
    }
}  // end VariableStack

